/*
 * Nor.c
 *
 *  Created on: Oct 13, 2018
 *      Author: ax
 */
#include <Hw/Emif.h>


#define ADDR_SPACE_MASK         (ADDR_SPACE_SIZE -1)
#define NOR_OFFSET(OFFSET)      ((OFFSET) & (ADDR_SPACE_MASK))

typedef enum {
    CMD_EREASE          = 0x80,
    CMD_CHIP_EREASE     = 0x10,
    CMD_SECTOR_ERASE    = 0x30,
    CMD_PROGRAM         = 0xA0,
    CMD_RESET2RD        = 0xF0,
    CMD_INVALID_CMD     = 0xFF,

    CMD_55 = 0x55,
    CMD_AA = 0xAA,

    ADDR_555 = 0x555,
    ADDR_2AA = 0x2AA,
    ADDR_AAA = 0xAAA,
} NorConst;

//AMD CFI
#define NOR_OFFSET_VAL(OFFSET)      *((volatile NorWord*) (NOR_BASE + NOR_OFFSET(OFFSET)))
#define NOR_CMD(OFFSET, VAL)        do {NOR_OFFSET_VAL(OFFSET) = (VAL);} while(0)
#if NOR_DATA_BITS == 8
#define CMD_CYCLE1                  NOR_CMD(ADDR_AAA, CMD_AA)
#define CMD_CYCLE2                  NOR_CMD(ADDR_555, CMD_55)
#define CMD_CYCLE_STD(CMD)          NOR_CMD(ADDR_AAA,  (CMD))
#define CMD_CYCLE_EXT(OFF, CMD)     NOR_CMD((OFF),     (CMD))
#define INVALID_ARGS(ADD, BYTES)    0
#else//NOR_DATA_BITS == 16
#define CMD_CYCLE1                  NOR_CMD(ADDR_555<<1, CMD_AA)
#define CMD_CYCLE2                  NOR_CMD(ADDR_2AA<<1, CMD_55)
#define CMD_CYCLE_STD(CMD)          NOR_CMD(ADDR_555<<1,  (CMD))
#define CMD_CYCLE_EXT(OFF, CMD)     NOR_CMD((OFF),        (CMD))
//address verify
#define INVALID_ARGS(ADD, BYTES)      ((ADD | BYTES) & 1)
#endif

extern void Nor_windowSelect(Uint32 offset);


static void cmdStd(NorConst cmd) {
    CMD_CYCLE1;
    CMD_CYCLE2;
    CMD_CYCLE_STD(cmd);
}

static void cmdExt(Uint32 offset, NorConst cmd) {
    CMD_CYCLE1;
    CMD_CYCLE2;
    Nor_windowSelect(offset);
    CMD_CYCLE_EXT(offset, cmd);
}


static short wordProgram(Uint32 offset, const NorWord nw) {
    Nor_windowSelect(0);
    cmdStd(CMD_PROGRAM);
    Nor_windowSelect(offset);
    CMD_CYCLE_EXT(offset, nw);

    do {;} while (NOR_OFFSET_VAL(offset) != nw);

    return 0;
}


EMIF_Status Nor_write(Uint32 offset, const NorWord* from, Uint32 nbytes) {
    if (INVALID_ARGS(offset, nbytes))
        return EMIF_INVALID_ARGS;

    NorWord* off = (NorWord*)offset;
    while (nbytes) {
        NorWord nw = *from++;
        wordProgram((Uint32  )off++, nw);
        nbytes -= sizeof(NorWord);
    }

    return EMIF_SOK;
}


EMIF_Status Nor_read(NorWord* to, Uint32 offset, Uint32 nbytes) {
    if (INVALID_ARGS(offset, nbytes))
        return EMIF_INVALID_ARGS;

    Uint32 termd = offset + nbytes;
    while (offset < termd) {
        Nor_windowSelect(offset);
        *to++ = NOR_OFFSET_VAL(offset);
        offset += sizeof(NorWord);
    }

    return EMIF_SOK;
}


EMIF_Status Nor_erase(Uint32 offset, Uint32 nbytes) {
    if ( ((offset +  nbytes) > NOR_SIZE)
            || (offset & (SECTOR_SIZE-1)) || (nbytes & (SECTOR_SIZE-1)) )
        return EMIF_INVALID_ARGS;

    if ((offset == 0) && (nbytes == NOR_SIZE)) {
        Nor_windowSelect(0);
        cmdStd(CMD_EREASE);
        cmdStd(CMD_CHIP_EREASE);
        while ((NOR_OFFSET_VAL(ADDR_AAA) & CMD_EREASE) != CMD_EREASE);
    } else {
        Uint16 sector;
        Uint16 sectorStart = offset / SECTOR_SIZE;
        Uint16 sectorEnd = sectorStart + (nbytes / SECTOR_SIZE);
        for (sector = sectorStart; sector < sectorEnd; sector++) {
            Uint32 off = sector * SECTOR_SIZE;
            Nor_windowSelect(0);
            cmdStd(CMD_EREASE);
            cmdExt(off, CMD_SECTOR_ERASE);
            while ((NOR_OFFSET_VAL(off) & CMD_EREASE) != CMD_EREASE);
        }
    }

    return EMIF_SOK;
}
